package core;

import java.util.*;
import java.util.concurrent.ConcurrentLinkedQueue;
import common.*;

public class Node extends Thread{
	private int pid;
	private long ip;
	private Router network;
	private Interface shell = null;
	
	private double utilization;
	private double power;
	
	private ArrayList<Resource> files;
	
	
	protected ArrayList<Worklet> request_q;
	protected ArrayList<Worklet> ready_q;
	protected ConcurrentLinkedQueue<Message> msg_q;
	
	public static int pid_acc = 0;
	private int master_pid = -1;
	private long master_ip = -1;
	
	private boolean running;
	
	public Node( Router _network ){
		// hw
		
		// sw
		pid = ++pid_acc;	//pid = ( new Random() ).nextInt( Conf.MAX_PID );

		// job q + msg q
		request_q = new ArrayList<Worklet>();
		ready_q = new ArrayList<Worklet>();
		msg_q = new ConcurrentLinkedQueue<Message>();
		
		// interface
		// delay instantiation for performance
		//shell = new Interface( this );
		
		// network
		network = _network;
		ip = network.connect( pid, this );	// pid is chosen by Node, ip is assigned by Router
		
		// utilization + power
		utilization = 0.0;
		power = Def.POWER_IDLE_WATT;
		
		// local files
		files = new ArrayList<Resource>();
		//files.add( new Resource( "lena_std.jpg" ) );
		files.add( new Resource( "lena_sm.jpg" ) );
		files.add( new Resource( "lena_tiny.jpg" ) );
		
		// ready to go
		running = true;
		
		print_log( "-proc(" + pid + ":" + Utils.ipToString( ip ) + "): init'ed + joined the network" );
	}
	
	public void run(){
		while( running ){
			// job
			if( !ready_q.isEmpty() ){
				// choose the shortest period one & run
				int cp = current_worklet();
				if( cp != -1 ){
					//System.out.println( "run(): sz=" + ready_q.size() + " cp=" + cp );
					
					boolean done = ready_q.get( cp ).exec();
					
					// when the task is done
					if( done ){
						// utilization + power level update
						utilization -= ready_q.get( cp ).get_wcet_over_period();
						if( utilization <= 0.5 ) power = Def.POWER_HALF_WATT;
						else if( utilization > 0.5 ) power = Def.POWER_MAX_WATT;
						
						// remove from ready_q
						Worklet result = ready_q.remove( cp );
						result.set_ts_done( System.currentTimeMillis() );
						
						// send to the requester
						Message msg = new Message( Def.RESULT, result.get_request_pid(), result.get_request_ip(), pid, ip, System.currentTimeMillis(), null, result );
						network.send( msg );
						
						print_log( "a task is done" );
					}
					
					
				}
				
				// update ready_q
				for( int i = 0; i < ready_q.size(); i++ ){
					ready_q.get( i ).update();
				}
			}
			
			// admission control
			if( request_q.size() > 0 ){
				int n = ready_q.size() + 1;
				if( utilization + request_q.get( 0 ).get_wcet_over_period() <= n * ( Math.pow( 2, 1/n ) - 1 ) ){
					// add to ready_q
					Worklet _request = request_q.remove( 0 );
					_request.set_ts_ready( System.currentTimeMillis() );
					ready_q.add( _request );
					
					// utilization + power level update
					utilization += _request.get_wcet_over_period();
					if( utilization <= 0.5 ) power = Def.POWER_HALF_WATT;
					else if( utilization > 0.5 ) power = Def.POWER_MAX_WATT;
					
					print_log( "a new task entered ready_q" );
				}else{
					// send to other
					print_log( "can't handle the request" );
				}
			}
			
			
			// msg
			Message incoming_msg = msg_q.poll();
			if( incoming_msg != null )
				msg_handler( incoming_msg );
		}
	}
	
	
	private void msg_handler( Message msg ){
		if( msg.get_type() == Def.MSG ){
			shell.show_message( msg );
		}else if( msg.get_type() == Def.REQUEST ){
			if( msg.get_attachment() != null && msg.get_attachment() instanceof Worklet ){
				request_q.add( ( Worklet )msg.get_attachment() );
				print_log( "received a request" );
			}
			
		}else{
			print_log( "unknown message type" );
		}
		return;
	}
	
	
	// msg dump
	public void print_log( String str ){
		Utils.print_log( "-proc(" + pid + ":" + Utils.ipToString( ip ) + "): " + str );
	}
	
	public void print_log( String str, byte target ){
		Utils.print_log( "-proc(" + pid + ":" + Utils.ipToString( ip ) + "): " + str, target );
	}
	
	// mutators
	public void set_master(){
		set_master( pid, ip );
	}
	
	public void set_master( int new_master_pid, long new_master_ip ){
		master_pid = new_master_pid;
		master_ip = new_master_ip;
		
		if( master_pid == pid && master_ip == ip ){
			shell = new Interface( this );
			shell.turnon();
		}
	}
	
	private boolean is_master(){
		return master_pid == pid && master_ip == ip;
	}
	
	// accessors
	public Router get_router(){ return network; }
	public Interface get_shell(){ return shell; }
	public long get_ip(){ return ip; }
	public int get_pid(){ return pid; }
	public double get_power(){ return power; }
	
	private int current_worklet(){
		int lowest_period = Integer.MAX_VALUE;
		int lowest_index = -1;
		for( int i = 0; i < ready_q.size(); i++ ){
			if( ready_q.get( i ).ready() && ready_q.get( i ).get_period() < lowest_period ){
				lowest_period = ready_q.get( i ).get_period(); 
				lowest_index = i;
			}
		}
		
		return lowest_index;
	}
}
